﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MathNet.Numerics.LinearAlgebra.Double;

namespace Marvin.Categorization.NeuronalNetworks
{
    public class LayerBuilder
    {
        private Layer _current; 
        public LayerBuilder(Layer current)
        {
            _current = current; 
        }

        public LayerBuilder WithHiddenLayer(int numberOfNeurons, Matrix parameters = null)
        {
            AddLayer<HiddenLayer>(numberOfNeurons, parameters);
            return this; 
        }

        public NeuralNetwork WithOutputLayer(int numberOfNeurons, Matrix parameters = null)
        {
            AddLayer<OutputLayer>(numberOfNeurons, parameters);
            var neuronalNetwork = CreateNeuralNetwork();
            return neuronalNetwork;
        }

        private void AddLayer<T>(int numberOfNeurons, Matrix parameters = null) where T : Layer
        {
            ThrowExceptionIfParametersAreInvalid(numberOfNeurons, parameters);
           
            var newLayer = (T)Activator.CreateInstance(typeof(T), numberOfNeurons, _current, parameters);

            newLayer.Previous = _current;
            _current.Next = newLayer;

            _current = newLayer;
        }

        private void ThrowExceptionIfParametersAreInvalid(int numberOfNeurons, Matrix parameters)
        {
            if (parameters == null)
                return;

            if(parameters.RowCount != numberOfNeurons)
                throw new ArgumentException(String.Format("The number of neurons ({0}) must equal the row count ({1}) of the parameter matrix.", numberOfNeurons, parameters.RowCount));

            if (parameters.ColumnCount != _current.NumberOfNeurons)
                throw new ArgumentException(String.Format("The number of neurons of the previous layer ({0}) must equal the column count ({1}) of the parameter matrix.", _current.NumberOfNeurons, parameters.ColumnCount));
        }

        private NeuralNetwork CreateNeuralNetwork()
        {
            var inputLayer = _current.GetInputLayer();
            var neuronalNetwork = new NeuralNetwork(inputLayer);
            return neuronalNetwork;
        }
    }
}
